package logic

import (
	"context"
	"encoding/json"
	"go.uber.org/zap"
	"strconv"
	"time"
	"wms/dao/mysql"
	"wms/dao/redis"
	"wms/define"
	"wms/models"
	"wms/pkg/snowflake"
)

// GetRoleList 根据page，size，keyword指定返回对应的角色列表，并且还会返回当前keyword匹配的所有角色的数量
func GetRoleList(p *define.ParamRoleListPage) (int64, []*define.RoleListReply, error) {
	var roleListCnt int64
	roleLists := make([]*define.RoleListReply, 0)
	tx := mysql.GetRoleListByKeyword(p.Keyword)
	err := tx.Count(&roleListCnt).Offset((p.Page - 1) * p.Size).Limit(p.Size).Find(&roleLists).Error
	if err != nil {
		zap.L().Error("mysql.GetRoleListByKeyword count failed", zap.Error(err))
		return 0, nil, err
	}

	// 修改返回前端的时间格式
	for _, v := range roleLists {
		v.CreatedAt = define.RFC3339ToNormalTime(v.CreatedAt)
		v.UpdatedAt = define.RFC3339ToNormalTime(v.UpdatedAt)
	}
	return roleListCnt, roleLists, nil
}

// CreateRole 创建新角色，需要操作 role_basic 和 role_menu 两张表
func CreateRole(p *define.ParamRoleAdd) error {
	// 本方法需要修改太多表，事务只在 role_basic role_menu 中处理了，没涉及其他表
	// 逻辑可能需要修改（如果后续出错了的话）
	// 1. 判断角色的名称唯一
	var rbCnt int64
	tx := mysql.GetRoleBasicByName(p.Name)
	if err := tx.Count(&rbCnt).Error; err != nil {
		zap.L().Error("mysql.GetRoleBasicByName count failed", zap.Error(err))
		return err
	}

	if rbCnt > 0 {
		// 角色名称重复
		zap.L().Error("CreateRole failed", zap.String("error", define.MysqlErrorRoleNameExist.Error()))
		return define.MysqlErrorRoleNameExist
	}

	// 2. 获取角色有权操作的菜单的id
	menuIdentitys := make([]string, 0)
	for identity, _ := range p.MapMenu {
		menuIdentitys = append(menuIdentitys, identity)
	}

	menuIdCruds := make([]*define.RoleMenuIdCRUD, 0)
	tx1 := mysql.GetMenuIdsByIdentitys(menuIdentitys)
	if err := tx1.Scan(&menuIdCruds).Error; err != nil {
		zap.L().Error("mysql.GetMenuIdsCURDsByIdentitys scan failed", zap.Error(err))
		return err
	}

	// 3. 构造角色信息，构造 role_basic 表的元素
	rb := &models.RoleBasic{
		Identity: strconv.FormatUint(snowflake.GetUint64ID(), 10),
		Name:     p.Name,
		IsAdmin:  p.IsAdmin,
		Sort:     p.Sort,
	}

	// 4. 创建中间表的元素，先填写好角色对应有权操作菜单的原生ID，menuId
	rm := make([]*models.RoleMenu, len(menuIdCruds))
	for i, _ := range rm {
		rm[i] = &models.RoleMenu{
			MenuId: menuIdCruds[i].Id,
			Crud:   p.MapMenu[menuIdCruds[i].Identity],
		}
		menuIdCruds[i].Crud = rm[i].Crud
	}

	// 5. 事务创建，创建角色 role_basic, role_menu
	if err := mysql.TransactionCreateRole(rb, rm); err != nil {
		zap.L().Error("mysql.TransactionCreateRole failed", zap.Error(err))
		return err
	}

	// 6. 获取角色对应 function_basic 所有有权访问 uri 的 functionId、uri
	funcIdUris := make([]*define.FuncIdUri, 0)
	for i, _ := range menuIdCruds {
		menuId := menuIdCruds[i].Id
		crud := menuIdCruds[i].Crud
		tmpFuncIdUris := make([]*define.FuncIdUri, 0)

		cruds := make([]int, 0)
		for j := 0; j < 4; j++ {
			if crud>>j&1 == 1 {
				cruds = append(cruds, 1<<j)
			}
		}

		tx := mysql.GetFuncIdUrisByMenuIdCruds(menuId, cruds)
		if err := tx.Scan(&tmpFuncIdUris).Error; err != nil {
			zap.L().Error("mysql.GetFuncUrisByCruds failed", zap.Error(err))
			continue
		}

		funcIdUris = append(funcIdUris, tmpFuncIdUris...)
	}

	// 7. 构建 role_function 数据存入
	roleFunc := make([]*models.RoleFunction, len(funcIdUris))

	for i, _ := range roleFunc {
		roleFunc[i] = &models.RoleFunction{
			RoleId:     rb.ID,
			FunctionId: funcIdUris[i].Id,
		}
	}

	if err := mysql.CreateRoleFunc(roleFunc); err != nil {
		zap.L().Error("mysql.CreateRoleFunc failed", zap.Error(err))
		return err
	}

	// 8. 获取角色有权操作的所有 uri
	uris := make(map[string]interface{}, len(funcIdUris))
	for i, _ := range funcIdUris {
		uris[funcIdUris[i].Uri] = "1"
	}

	// 9. 插入 redis
	funcKey := define.RedisFuncPrefix + rb.Identity
	_ = redis.RDB.HSet(context.Background(), funcKey, uris).Err()
	return nil
}

// DeleteRoleByIdentity 根据 identity 删除对应角色
func DeleteRoleByIdentity(identity string) error {
	// 1. 判断当前角色是否已关联用户
	var cnt int64
	tx := mysql.GetUserBasicsByRoleIdentity(identity)
	if err := tx.Count(&cnt).Error; err != nil {
		zap.L().Error("mysql.GetUsersByRoleIdentity find failed", zap.Error(err))
		return err
	}
	if cnt > 0 {
		zap.L().Warn("DeleteRoleByIdentity", zap.String("error", define.MysqlErrorUserExistWithRole.Error()))
		return define.MysqlErrorUserExistWithRole
	}

	// 2. 获取 role_basic 的原生 id
	var rbId uint
	tx1 := mysql.GetRoleBasicIdByIdentity(identity)
	if err := tx1.Find(&rbId).Error; err != nil {
		zap.L().Error("mysql.GetRoleBasicIdByIdentity find failed", zap.Error(err))
		return err
	}

	// 3. 事务删除，role_basic，role_menu，role_function
	if err := mysql.TransactionDeleteRole(identity, rbId); err != nil {
		zap.L().Error("mysql.TransactionDeleteRole failed", zap.Error(err))
		return err
	}

	// 4. 删除角色相关缓存
	if err := redis.RedisRoleDelete(identity); err != nil {
		zap.L().Error("redis.RedisRoleDelete failed", zap.Error(err))
		return err
	}

	return nil
}

// GetRoleDetail 获取角色详情
func GetRoleDetail(identity string, p *define.RoleDetailReply) error {
	// 思考：这里是不是可以从 redis 获取当前角色的菜单信息
	var rb models.RoleBasic
	// 获取 role_basic 角色基本信息
	tx := mysql.GetRoleBasicByIdentity(identity)
	if err := tx.Find(&rb).Error; err != nil {
		return err
	}
	// 获取 role_menu 中当前 role 对应的所有 menus，授权菜单的 identitys - crud

	roleMenus := make([]struct {
		Identity string
		Crud     uint
	}, 0)
	tx1 := mysql.GetRoleMenusByRoleId(rb.ID)
	if err := tx1.Scan(&roleMenus).Error; err != nil {
		return err
	}

	// 获取到的信息存入 define.RoleDetailReply
	p.Name = rb.Name
	p.Sort = rb.Sort
	p.IsAdmin = rb.IsAdmin

	mapMenu := make(map[string]uint, 0)
	for _, v := range roleMenus {
		mapMenu[v.Identity] = v.Crud
	}
	p.MapMenu = mapMenu
	b, _ := json.Marshal(p.MapMenu)
	p.MapMenuStr = string(b)
	return nil
}

// UpdateRoleDetail 更新角色详情信息
func UpdateRoleDetail(p *define.ParamRoleDetail) error {
	// 本方法会用到：延迟双删（更删删），尽可能保证 redis-mysql 数据一致性。
	// 1. 角色名称判重
	var rbCnt int64
	if err := mysql.GetRoleBasicByNameWithoutIdentity(p.Identity, p.Name).Count(&rbCnt).Error; err != nil {
		zap.L().Error("GetRoleBasicByName failed", zap.Error(err))
		return err
	}
	if rbCnt > 1 {
		zap.L().Error("UpdateRoleDetail failed", zap.String("error", define.MysqlErrorRoleNameExist.Error()))
		return define.MysqlErrorRoleNameExist
	}

	// 2. 获取角色原生 id，role_id
	// 直接获取整个结构，用于后续 mysql 层调用
	rb := new(models.RoleBasic)
	tx := mysql.GetRoleBasicByIdentity(p.Identity)
	if err := tx.Find(rb).Error; err != nil {
		return err
	}
	// 记得更新前端传上来的字段
	rb.Sort = p.Sort
	rb.IsAdmin = p.IsAdmin
	rb.Name = p.Name

	// 3. 获取角色对应菜单表的所有原生 id，menu_id
	menuIdentitys := make([]string, 0)
	for identity, _ := range p.MapMenu {
		menuIdentitys = append(menuIdentitys, identity)
	}

	menuIdCruds := make([]*define.RoleMenuIdCRUD, 0)
	tx1 := mysql.GetMenuIdsByIdentitys(menuIdentitys)
	if err := tx1.Scan(&menuIdCruds).Error; err != nil {
		zap.L().Error("mysql.GetMenuIdsCURDsByIdentitys scan failed", zap.Error(err))
		return err
	}

	// 4. 组装 role_menu 数据
	rm := make([]*models.RoleMenu, len(menuIdCruds))
	for i, _ := range rm {
		rm[i] = &models.RoleMenu{
			RoleId: rb.ID,
			MenuId: menuIdCruds[i].Id,
			Crud:   p.MapMenu[menuIdCruds[i].Identity],
		}
		menuIdCruds[i].Crud = rm[i].Crud
	}

	// 5. 根据 menu_id 和 crud 从 function_basic 中获取 function_id
	funcIdUris := make([]*define.FuncIdUri, 0)
	for i, _ := range menuIdCruds {
		menuId := menuIdCruds[i].Id
		crud := menuIdCruds[i].Crud
		tmpFuncIdUris := make([]*define.FuncIdUri, 0)

		cruds := make([]int, 0)
		for j := 0; j < 4; j++ {
			if crud>>j&1 == 1 {
				cruds = append(cruds, 1<<j)
			}
		}
		tx := mysql.GetFuncIdsByMenuIdCruds(menuId, cruds)
		if err := tx.Scan(&tmpFuncIdUris).Error; err != nil {
			zap.L().Error("mysql.GetFuncUrisByCruds failed", zap.Error(err))
			continue
		}
		funcIdUris = append(funcIdUris, tmpFuncIdUris...)
	}

	// 6. 组装 role_function 数据
	rf := make([]*models.RoleFunction, len(funcIdUris))

	for i, _ := range rf {
		rf[i] = &models.RoleFunction{
			RoleId:     rb.ID,
			FunctionId: funcIdUris[i].Id,
		}
	}

	// 7. 事务更新 mysql 数据，role_basic，role_menu，role_function
	if err := mysql.TransactionUpdateRole(rb, rm, rf); err != nil {
		zap.L().Error("mysql.TransactionUpdateRole failed", zap.Error(err))
		return err
	}

	// 8. 删除 redis 缓存的角色数据信息
	if err := redis.RedisRoleDelete(p.Identity); err != nil {
		return err
	}

	// 9. 延迟双删
	go func() {
		time.Sleep(time.Millisecond * 200)
		_ = redis.RedisRoleDelete(p.Identity)
	}()
	return nil
}

// UpdateRoleAdmin 修改角色的管理员权限
func UpdateRoleAdmin(identity string, isAdmin int8) error {
	adminRoleKey := define.RedisRoleAdminPrefix + identity
	// 1. 更新 mysql 的 role_basic 表该角色的 is_admin 状态
	if err := mysql.UpdateRoleAdminByIdentity(identity, isAdmin); err != nil {
		zap.L().Error("mysql.UpdateRoleAdminByIdentity failed", zap.Error(err))
		return err
	}

	// 2. 删除 redis 的 ADMIN-roleIdentity 键值对
	if err := redis.RDB.Del(context.Background(), adminRoleKey).Err(); err != nil {
		zap.L().Error("redis del adminRoleKey failed", zap.String("ADMIN-roleIdentity", adminRoleKey), zap.Error(err))
		return err
	}

	// 3. 延迟双删
	go func() {
		time.Sleep(200 * time.Millisecond)
		_ = redis.RDB.Del(context.Background(), adminRoleKey).Err()
	}()
	return nil
}
